package sound;

/**
 * @author Yogeswar Vijayakumar
 * class: PcmMixer
 * Purpose: This class will mix a number of input audio files into a single audio file
 * Input File Format : PCM-signed
 * Input File Type : File, ArrayList of File, filepath, an array containing multiple filepaths
 * References :  
 * 1. Viktor T. Toth - Mixing Digital Audio - http://www.vttoth.com/CMS/index.php/technical-notes/68
 * 2. Programming Forum - Sebastiani - http://cboard.cprogramming.com/c-programming/103456-mixing-pcm-samples-dealing-clicks-overflow.html
 * 3. Richard G. Baldwin - Java Sound Tutorial - http://www.developer.com/java/other/article.php/1565671/Java-Sound-An-Introduction.htm
 * 
 */


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.sound.sampled.AudioFileFormat;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.UnsupportedAudioFileException;

public class PcmMixer {
	
	private AudioFormat audioFormat;
	private AudioInputStream audioInputStream;
	private List<AudioInputStream> aisList;
	private File outputFile;
	private File inputFile;
	private List<File> inputFilesList;
	private static final AudioFileFormat.Type fileType = AudioFileFormat.Type.AU;
	private AudioInputStream ais_out;
	
	public PcmMixer()
	{
		
	}
	
	public PcmMixer(ArrayList<File> inputFiles)
	{
		this.inputFilesList = inputFiles;
	}
	
	public PcmMixer(File ipFile)
	{
		this.addFile(ipFile);			
	}
	
	public PcmMixer(String path)
	{
		this.addFile(path);			
	}
	
	public PcmMixer(String[] path)
	{
		this.addFile(path);			
	}
	
	public void addFile(File ipFile)
	{
		if(this.inputFilesList == null)
		{
			this.inputFilesList = new ArrayList<File>();
		}
		this.inputFilesList.add(ipFile);
	}
	
	public void addFile(String path)
	{
		File ipFile = new File(path);
		this.addFile(ipFile);
	}
	
	public void addFile(String[] path)
	{
		for(int i=0; i<path.length;i++)
		{
			this.addFile(path[i]);
		}
	}
	
	public void mix()
	{
		if(this.outputFile == null)
		{
			this.outputFile = new File("mixed.au");
		}
		
		if(this.outputFile.exists())
		{
			String path = this.outputFile.getPath();
			this.outputFile.delete();
			this.outputFile = new File(path);		
		}
		
		this.createAudioInputStreamList();
		this.addAudioStreams();
	}
	
	private void addAudioStreams()
	{
		System.out.println("In addAudioStreams()");
		int counter1=-1, counter2=-1;
		byte[] buffer = new byte[10000];
		byte[] outputBuffer = new byte[10000];
		Iterator aisIterator = this.aisList.iterator();
		ByteArrayInputStream b_in;
		ByteArrayOutputStream b_out = new ByteArrayOutputStream();
		b_in = new ByteArrayInputStream(b_out.toByteArray());
		this.ais_out = new AudioInputStream(b_in,this.audioFormat,this.audioInputStream.getFrameLength());
		
		while(aisIterator.hasNext())
		{
			b_out = new ByteArrayOutputStream();
			
			this.audioInputStream = (AudioInputStream) aisIterator.next();
					
			do
			{
				try {
					Arrays.fill(outputBuffer, (byte)0);
					Arrays.fill(buffer, (byte)0);
					counter1 = this.ais_out.read(outputBuffer,0,outputBuffer.length);
					counter2 = this.audioInputStream.read(buffer,0,buffer.length);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					counter1=counter2=-1;
				}
				
				if(counter1 > 0 || counter2 > 0)
				{
					for(int i=0,j=0; i<outputBuffer.length || j<buffer.length;i++,j++)
					{
						outputBuffer[i] = (byte) ((outputBuffer[i] + buffer[j]) - ((outputBuffer[i] * buffer[j]) >> 0x10));
					}
					
					try {
						b_out.write(outputBuffer);
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
				
			}while(counter1 != -1 || counter2 != -1);
			
			b_in = new ByteArrayInputStream(b_out.toByteArray());
			this.ais_out = new AudioInputStream(b_in,this.audioFormat,this.audioInputStream.getFrameLength());
		}
		
		try {
			AudioSystem.write(this.ais_out, fileType, this.outputFile);
			System.out.println("outputfile written : " + this.outputFile.getAbsolutePath());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private void createAudioInputStreamList()
	{
		System.out.println("In createAudioInputStreamList()");
		Iterator fileIterator = this.inputFilesList.iterator();
		while(fileIterator.hasNext())
		{
			this.inputFile = (File) fileIterator.next();
			
			try 
			{
				this.audioInputStream = AudioSystem.getAudioInputStream(this.inputFile);
			} catch (UnsupportedAudioFileException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			if(this.audioFormat == null)
			{
				this.audioFormat = this.audioInputStream.getFormat();
			}
			
			try
			{
				if(! this.audioFormat.matches(this.audioInputStream.getFormat()))
				{
					throw new Exception("Audio Format does not match");
				}
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
			
			if(this.aisList == null)
			{
				this.aisList = new ArrayList<AudioInputStream>();
			}
			this.aisList.add(this.audioInputStream);
		}
	}
	
	public File getOutputFile()
	{
		File opFile = this.outputFile;
		return opFile;
	}
	
	public String getOutputFilePath()
	{
		return this.outputFile.getAbsolutePath();
	}
	
	public void setOutputFile(String filepath)
	{
		this.outputFile = new File(filepath);
	}
	
	public static void main(String args[])
	{
		System.out.println("Testing class pcmmixer");
		String[] files = new String[3];
		files[0] = "recorded.au";
		files[1] = "junk2.au";
		files[2] = "junk3.au";
		PcmMixer testMixer = new PcmMixer(files);
		testMixer.setOutputFile("mixed3.au");
		testMixer.mix();
	}
}
